//#include rfm

(function() {

/**
 * Whether or not the event has been stopped
 */
Event.prototype.$stopped = false;

/**
 * Stops all activity for an event
 */
Event.prototype.$stop = function() {
	this.preventDefault();
	this.stopPropagation();
	this.$stopped = true;
};

/** 
 * Adds an event listener to an element that executes at most one time
 * @param {String} eventName The name of the event to listen for
 * @param {Function} callback The callback to execute when the event is dispatched
 * @param {Boolean} capture (Optional) Whether or not to trigger in the capturing phase. Defaults to false
 * @returns {Element} The element
 */
Element.prototype.$once = function(eventName, callback, capture) {
	this.addEventListener(eventName, function(e) {
		if (!e.$stopped) {
			callback.call(this, e);
			this.removeEventListener(eventName, arguments.callee, capture === true);
		}
	}, capture === true);
	return this;
};

/** 
 * Adds an event listener to an element
 * @param {String} eventName The name of the event to listen for
 * @param {Function} callback The callback to execute when the event is dispatched
 * @param {Boolean} capture (Optional) Whether or not to trigger in the capturing phase. Defaults to false
 * @returns {Element} The element
 */
Element.prototype.$bind = function(eventName, callback, capture) {
	this.addEventListener(eventName, function(e) {
		if (!e.$stopped) {
			callback.call(this, e);
		}
	}, capture === true);
	return this;
};

/** 
 * Adds an event listener to an element's click event and overrides default behavior
 * @param {Function} callback The callback to execute when the event is dispatched
 * @returns {Element} The element
 */
Element.prototype.$click = function(callback) {
	this.addEventListener('click', function(e) {
		if (!e.$stopped) {
			e.preventDefault();
			callback.call(this, e);
		}
	}, false);
	return this;
};

/**
 * Dispatches an event
 * @param {String} eventName The name of the event to dispatch
 * @returns {Element} The element
 */
Element.prototype.$dispatch = function(eventName) {
	var evt = document.createEvent('HTMLEvents');
	evt.initEvent(eventName, true, false);
	evt.$artificial = true;
	this.dispatchEvent(evt);
	return evt;
};

/**
 * DOM utility object
 */
rfm.DOM = {
    /**
     * Creates an HTML element
     * @param {String} nodeName The name of the element (ie, "IMG")
     * @param {Object} attributes (Optional) A hash of attribute names and values to apply to the element
     * @param {String} innerHTML (Optional) HTML content for the new element
     * @returns {Element} The new element
     */
    create: function(nodeName, attributes, innerHTML) {
        var element = document.createElement(nodeName),
            attribute;
        if (attributes) {
            for (attribute in attributes) {
                element.setAttribute(attribute, attributes[attribute]);
            }
        }
        if (innerHTML) {
        	if (innerHTML instanceof Element) {
        		element.appendChild(innerHTML);
        	}
        	else if (innerHTML instanceof Array) {
        		for (var i = 0, j = innerHTML.length; i < j; i++) {
        			element.appendChild(innerHTML[i]);
        		}
        	}
        	else {
        		element.innerHTML = innerHTML;
        	}
        }
        return element;
    },
    /**
     * Parses HTML code
     * @param {String} code The raw HTML code to convert into DOM objects
     * @returns {Array<Element>} An array containing the top-level elements that were created from the HTML code
     */
    parse: function(code) {
        var results = [],
            div = document.createElement('div'),
            node,
            next;
        div.innerHTML = code;
        node = div.firstChild;
        while (node) {
            next = node.nextSibling;
            if (node.nodeType == 1) {
                node.parentNode.removeChild(node);
                results[results.length] = node;
            }
            node = next;
        }
        return results;
    },
	/**
	 * Alias for document.querySelector
	 * @param {String} selector The CSS selector
	 * @param {Node} context (Optional) The context (root node) of the query
	 * @returns {Element} First element that matches the selector
	 */
	first: function(selector, context) {
	    context = context || document;
	    if (arguments.length < 2 && /^\#[-\w]+$/.test(selector)) {
	        return document.getElementById(selector.replace('#', ''));
	    }
	    else if (context.getElementsByTagName && /^[-\w]+$/.test(selector)) {
	        return context.getElementsByTagName(selector)[0];
	    }
	    else {
		    return context.querySelector(selector);
	    }
	},
	/**
	 * Alias for document.querySelectorAll
	 * @param {String} selector The CSS selector
	 * @param {Node} context (Optional) The context (root node) of the query
	 * @returns {NodeList<Element>} All elements matching the selector
	 */
	all: function(selector, context) {
	    context = context || document;
	    if (context.getElementsByClassName && /^\.[-\w]+$/.test(selector)) {
	        return context.getElementsByClassName(selector.replace('.', ''));
	    }
	    else if (context.getElementsByTagName && /^[-\w]+$/.test(selector)) {
	        return context.getElementsByTagName(selector);
	    }
	    else {
		    return context.querySelectorAll(selector);
		}
	},
	/** 
	 * Queries the DOM for nodes matching a CSS selector and performs a callback for each of them
	 * @param {String} selector The CSS selector
	 * @param {Function} callback The function to execute for each node
	 * @param {Node} context (Optional) The context (root node) of the query
	 * @returns {Array<Element>} An array of elements matching the query
	 */
	each: function(selector, callback, context) {
		var nodes = Array.prototype.slice.call(rfm.DOM.all(selector, context)),
			i = [],
			j;
		for (i = 0, j = nodes.length; i < j; i++) {
		    callback.call(nodes, nodes[i], i, nodes);
	    }
		return nodes;
	}
};

/**
 * Alias for Element.prototype.querySelector
 * @param {String} selector The CSS selector
 * @returns {Node} First node that matches the selector
 */
Element.prototype.$first = function(selector) {
    return rfm.DOM.first(selector, this);
};

/**
 * Alias for Element.prototype.querySelectorAll
 * @param {String} selector The CSS selector
 * @returns {Node} First node that matches the selector
 */
Element.prototype.$all = function(selector) {
    return rfm.DOM.all(selector, this);
};

/** 
 * Queries the DOM for nodes matching a CSS selector and performs a callback for each of them
 * @param {String} selector The CSS selector
 * @param {Function} callback The function to execute for each node
 * @returns {Array<Element>} An array of elements matching the query
 */
Element.prototype.$each = function(selector, callback) {
    return rfm.DOM.each(selector, callback, this);
};

/**
 * Finds all direct child elements of the element
 * @param {String} selector (Optional) A CSS selector that the element must match
 * @returns {Array<Element>} A collection of elements with that tag name
 */
Element.prototype.$children = function(selector) {
	var result = [],
		node = this.firstChild,
		found = selector ? rfm.DOM.all(selector, this) : null;
	while (node) {
		if (node.nodeType == 1) {
			if (selector) {
				if (result.indexOf.call(found, node) > -1) {
					result.push(node);
				}
			} else {
				result.push(node);
			}
		}
		node = node.nextSibling;
	}
	return result;
};

/**
 * Inserts a new element into this element's hierarchy. If referenceElement is null, the node is inserted at the end of the tree
 * @param {Element} newElement The node to insert
 * @param {Element} referenceElement The node after which newElement is inserted
 * @returns {Element} The newly inserted element
 */
Element.prototype.$insertAfter = function(newElement, referenceElement) {
    this.insertBefore(newElement, referenceElement ? referenceElement.nextSibling : null);
    return newElement;
};

/**
 * Finds the first parent of an element that has a given tag name
 * @param {String} nodeName The name of the parent element's tag
 * @returns {Element} The first parent node with that tag name
 */
Node.prototype.$parentByTagName = function(nodeName) {
	nodeName = nodeName.toUpperCase();
	var node = this,
	    found = false;
	while (node = node.parentNode) {
		if (node.nodeName === nodeName) {
		    found = true;
			break;
		}
	}
	return found ? node : null;
};

/**
 * Finds the first parent of an element that has a given class name
 * @param {String} className The class name of the parent element
 * @returns {Element} The first parent node with that class name
 */
Node.prototype.$parentByClassName = function(className) {
	var node = this,
	    found = false,
	    regex = new RegExp('(^|\\s+)' + className + '(\\s+|$)', 'g');
	while (node = node.parentNode) {
		if (node.nodeType == 1 && regex.test(node.className)) {
		    found = true;
			break;
		}
	};
	return found ? node : null;
};

/**
 * Detects whether or not a specified element is a descendent of this one
 * @param {Node} other The node in question
 * @returns {Boolean} True if the node is an ancestor of this element 
 */
Node.prototype.$contains = function(other) {
	var parent = other;
	while (parent = parent.parentNode) {
		if (parent == this) {
			return true;
		}
	}
	return false;
};

if (!NodeList.prototype.forEach) {
    /**
     * Executes a provided function once per node list member
     * @param {Function} callback Function to execute for each element
     * @param {Object} thisObject (Optional) Object to use as this when executing callback
     */
    NodeList.prototype.forEach = function(callback/*, thisObject*/) {
        Array.prototype.forEach.apply(Array.prototype.slice.call(this), arguments);
    };
}

})();